<?php
	final class MySQLAssistant extends MySQLProcess implements SqlDefaults, SqlSelect, SqlInsert, SqlUpdate, SqlDelete, SqlAvanceds {
		/* estado de la transaccion */
		protected $transaccion = False;

		public function setWhere($where = False) {
			try {
				/* mostramos un mensaje para identificar que estamos haciendo */
				$this->show('Asignando filtro WHERE..', False);
				/* verificamos que los parametros sean correctos */
				$toAdd = $this->validarWhere($where);
				/* agregamos el WHERE a la consulta */
				$this->armarWhere($where, $toAdd);
				/* mostramos el OK al mensaje */
				$this->showOK(True);
			} catch (MyException $e) {
				$this->show($e->getMensaje($this->html));
			}
		}

		public function setOrderBy($order = False, $asc = True) {
			try {
				/* mostramos un mensaje para identificar que estamos haciendo */
				$this->show('Asignando ORDER BY..', False);
				/* verificamos los datos */
				$this->validarOrderBy($order, $asc);
				/* armamos el ORDER para la consulta */
				$this->armarOrderBy($order, $asc);
				/* mostramos el OK al mensaje */
				$this->showOK(True);
			} catch (MyException $e) {
				$this->show($e->getMensaje($this->html));
			}
		}

		public function setLimit($limit = False, $cantidad = False) {
			try {
				/* mostramos un mensaje para identificar que estamos haciendo */
				$this->show('Asignando LIMIT..', False);
				/* validamos los datos */
				$this->validarLimit($limit, $cantidad);
				/* armamos el LIMIT */
				$this->armarLimit($limit, $cantidad);
				/* mostramos el OK al mensaje */
				$this->showOK(True);
			} catch (MyException $e) {
				$this->show($e->getMensaje($this->html));
			}
		}

		public function selectFrom($campos = False, $tabla = False, $showMy = True) {
			try {
				/* mostramos un mensaje para identificar que estamos haciendo */
				if ($showMy)
					$this->show('Armando consulta SELECT..', False);
				/* verificamos si existe una conexion */
				$this->verificarConexion();
				/* verificamos que se envio un nombre de tabla */
				if ($this->validarSelectFrom($campos, $tabla)) {
					$tabla = $campos;
					$campos = False;
				}
				/* buscamos por un SQL sin ejecutar */
				$this->buscarSQL(True);
				/* armamos el SELECT */
				$this->armarSelectFrom($campos, $tabla, $showMy);
				/* mostramos el OK al mensaje */
				if ($showMy)
					$this->showOK(True);
			} catch (MyException $e) {
				if ($showMy)
					/* si puedo mostrar errores lo muestro */
					$this->show($e->getMensaje($this->html));
				else
					/* sino, produzco uno para que salte al try catch del metodo que me llamo */
					$this->newException($e->getMensaje());
			}
		}

		public function selectFromJoin($campos = False, $deTabla = False, $tablas = False) {
			try {
				/* mostramos un mensaje para identificar que hacemos */
				$this->show('Armando consulta SELECT con JOIN..', False);
				/* verificamos si existe una conexion */
				$this->verificarConexion();
				/* armamos la consulta con JOIN */
				$this->armarSelectFromJoin($campos, $deTabla, $tablas);
				/* mostramos el OK al mensaje */
				$this->showOK(True);
			} catch (MyException $e) {
				$this->show($e->getMensaje($this->html));
			}
		}

		public function insertInto($tabla = False, $datos = False) {
			try {
				/* mostramos un mensaje para identificar que hacemos */
				$this->show('Armando consulta INSERT INTO..', False);
				/* verificamos si existe una conexion */
				$this->verificarConexion();
				/* verificamos los valores recibidos */
				$this->validarInsert($tabla, $datos);
				/* buscamos por un SQL sin ejecutar */
				$this->buscarSQL(True);
				/* comenzamos a armar la consulta SQL */
				$this->armarInsert($tabla, $datos);
				/* mostramos el OK al mensaje */
				$this->showOK(True);
			} catch (MyException $e) {
				$this->show($e->getMensaje($this->html));
			}
		}

		public function insertIntoFields($tabla = False, $datos = False) {
			try {
				/* mostramos un mensaje para identificar que hacemos */
				$this->show('Armando consulta INSERT INTO con especificacion de campos..', False);
				/* verificamos si existe una conexion */
				$this->verificarConexion();
				/* verificamos los valores recibidos */
				$this->validarInsert($tabla, $datos, True);
				/* buscamos por un SQL sin ejecutar */
				$this->buscarSQL(True);
				/* comenzamos a armar la consulta SQL */
				$this->armarInsertFields($tabla, $datos);
				/* mostramos el OK al mensaje */
				$this->showOK(True);
			} catch (MyException $e) {
				$this->show($e->getMensaje($this->html));
			}
		}

		public function updateSet($tabla = False, $campos = False) {
			try {
				/* mostramos un mensaje para identificar que hacemos */
				$this->show('Armando consulta UPDATE..', False);
				/* verificamos si existe una conexion */
				$this->verificarConexion();
				/* verificamos los valores recibidos */
				$this->validarUpdate($tabla, $campos);
				/* buscamos por un SQL sin ejecutar */
				$this->buscarSQL(True);
				/* armamos la consulta SQL */
				$this->armarUpdate($tabla, $campos);
				/* mostramos el OK al mensaje */
				$this->showOK(True);
			} catch (MyException $e) {
				$this->show($e->getMensaje($this->html));
			}
		}

		public function deleteFrom($tabla = False) {
			try {
				/* mostramos un mensaje para identificar que hacemos */
				$this->show('Armando consulta DELETE FROM..', False);
				/* verificamos si existe una conexion */
				$this->verificarConexion();
				/* verificamos los valores recibidos */
				$this->validarDelete($tabla);
				/* buscamos por un SQL sin ejecutar */
				$this->buscarSQL(True);
				/* armamos la consulta SQL */
				$this->addSQL("DELETE FROM $tabla", True);
				/* mostramos el OK al mensaje */
				$this->showOK(True);
			} catch (MyException $e) {
				$this->show($e->getMensaje($this->html));
			}
		}

		public function iniciarTransaccion() {
			try {
				/* mostramos un mensaje para identificar que hacemos */
				$this->show('Iniciando transaccion..', False);
				/* verificamos si existe una conexion */
				$this->verificarConexion();
				/* verificamos si ya no hay una transaccion en proceso */
				if ($this->transaccion)
					$this->newException('Ya existe una transaccion en progreso');
				/* buscamos por un SQL sin ejecutar */
				$this->buscarSQL(True);
				/* armamos el SQL */
				$this->addSQL("SET AUTOCOMMIT=0", True);
				/* mostramos el OK al mensaje */
				$this->showOK(True);
				/* ejecutamos la consulta */
				$this->ejecutarSQL();
				/* armamos el SQL */
				$this->addSQL("START TRANSACTION", True);
				/* ejecutamos la consulta */
				$this->ejecutarSQL();
				/* iniciamos la transaccion */
				$this->transaccion = True;
			} catch (MyException $e) {
				$this->show($e->getMensaje($this->html));
			}
		}

		public function finalizarTransaccion() {
			try {
				/* mostramos un mensaje para identificar que hacemos */
				$this->show('Finalizando transaccion..', False);
				/* verificamos si existe una conexion */
				$this->verificarConexion();
				/* verificamos si ya no hay una transaccion en proceso */
				if (!$this->transaccion)
					$this->newException('No existe ninguna transaccion en progreso');
				/* buscamos por un SQL sin ejecutar */
				$this->buscarSQL(True);
				/* armamos el SQL */
				$this->addSQL("COMMIT", True);
				/* mostramos el OK al mensaje */
				$this->showOK(True);
				/* ejecutamos la consulta */
				$this->ejecutarSQL();
				/* armamos el SQL */
				$this->addSQL("SET AUTOCOMMIT=1", True);
				/* ejecutamos la consulta */
				$this->ejecutarSQL();
				/* finalizamos la transaccion */
				$this->transaccion = False;
			} catch (MyException $e) {
				$this->show($e->getMensaje($this->html));
			}
		}

		public function cancelarTransaccion() {
			try {
				/* mostramos un mensaje para identificar que hacemos */
				$this->show('Cancelando transaccion..', False);
				/* verificamos si existe una conexion */
				$this->verificarConexion();
				/* verificamos si ya no hay una transaccion en proceso */
				if (!$this->transaccion)
					$this->newException('No existe ninguna transaccion en progreso');
				/* buscamos por un SQL sin ejecutar */
				$this->buscarSQL(True);
				/* armamos el SQL */
				$this->addSQL("ROLLBACK", True);
				/* mostramos el OK al mensaje */
				$this->showOK(True);
				/* ejecutamos la consulta */
				$this->ejecutarSQL();
				/* armamos el SQL */
				$this->addSQL("SET AUTOCOMMIT=1", True);
				/* ejecutamos la consulta */
				$this->ejecutarSQL();
				/* finalizamos la transaccion */
				$this->transaccion = False;
			} catch (MyException $e) {
				$this->show($e->getMensaje($this->html));
			}
		}

		private function validarOrderBy($order, $asc) {
				/* verificamos que se envio el parametro ORDER BY */
				if ($order === False)
					$this->newException('No se especifico el campo para asignar el orden');
				/* verificamos si existe una consulta para agregar WHERE */
				if (!$this->existeSQL())
					$this->newException('No existe una consulta para agregar la clausula ORDER BY');
				/* verificamos que se envio correctamente los parametros */
				if (gettype($order) != 'array')
					$this->newException('El valor para ORDER BY debe ser enviado por medio de un Array');
				if (gettype($asc) != 'boolean')
					$this->newException('El valor de orden debe ser especificado por medio de un valor booleano');
				/* verificamos que no tenga ya un limite */
				if (strstr(strtolower($this->tempSQL), 'limit'))
					$this->newException('No se puede agregar un ORDER BY luego de la existencia de un LIMIT');
		}

		private function validarLimit($limit, $cantidad) {
			/* verificamos si envio un parametro LIMIT */
			if ($limit === False)
				$this->newException('No se especifico la cantidad para LIMIT');
			/* verificamos si es entero */
			if (gettype($limit) != 'integer')
				$this->newException('El valor para LIMIT debe ser entero');
			/* verificamos que no se alla enviado un valor negativo */
			if ($limit < 0 || ($cantidad !== False && $cantidad < 0))
				$this->newException('El valor para LIMIT debe ser mayor a cero');
			/* verificamos si existe una consulta para agregar LIMIT */
			if (!$this->existeSQL())
				$this->newException('No existe una consulta para asignar LIMIT');
			/* verificamos que no tenga ya un limite */
			if (strstr(strtolower($this->tempSQL), 'limit'))
				$this->newException('La consulta actual ya posee un valor para LIMIT');
		}

		private function validarWhere($where) {
			$toAdd = ' WHERE ';
			/* verificamos si envio como parametro algun WHERE */
			if ($where === False)
				$this->newException('No se especifico clausula WHERE');
			/* verificamos que se envio un string */
			if (gettype($where) != 'string')
				$this->newException('La clausula WHERE debe ser un string');
			/* verificamos si existe una consulta para agregar WHERE */
			if (!$this->existeSQL())
				$this->newException('No existe una consulta para agregar la clausula WHERE');
			/* verificamos que no sea un INSERT */
			if (strstr(strtolower($this->tempSQL), 'insert'))
				/* enviamos un error */
				$this->newException('No se puede agregar un filtro WHERE en una consulta INSERT');
			/* verificamos que no exista un LIMIT */
			if (strstr(strtolower($this->tempSQL), 'limit'))
				$this->newException('No se puede agregar un filtro WHERE luego de la existencia de un LIMIT');
			/* verificamos que no exista un ORDER BY */
			if (strstr(strtolower($this->tempSQL), 'order by'))
				$this->newException('No se puede agregar un filtro WHERE luego de la existencia de un ORDER BY');
			/* verificamos que no tenga ya un WHERE */
			if (strstr(strtolower($this->tempSQL), 'where'))
				$toAdd = ' AND ';
			return $toAdd;
		}

		private function validarSelectFrom($campos, $tabla) {
			/* verificamos si solo se envio el nombre de la tabla */
			if ($tabla === False && gettype($campos) == 'string')
				return True;
			else
				/* verificamos el nombre de la tabla */
				$this->validarNombre($tabla);
			return False;
		}

		private function validarUpdate($tabla, $campos) {
			/* verificamos que se envio un nombre de tabla */
			$this->validarNombre($tabla);
			/* verificamos los campos */
			if ($campos === False)
				/* mostramos un mensaje de error */
				$this->newException('No se especifico los datos a actualizar');
			if (gettype($campos) != 'array')
				/* mostramos un mensaje de error */
				$this->newException('Los datos a almacenar deben ser pasados como Array');

			/* recorremos los datos para verificar en indice */
			foreach ($campos AS $indice => $valor) {
				if (gettype($indice) != 'string')
					/* mostramos un mensaje de error */
					$this->newException('El indice de los datos deben ser pasados como String');
			}
		}

		private function validarDelete($tabla) {
			/* verificamos que se envio un nombre de tabla */
			$this->validarNombre($tabla);
		}

		private function validarNombre($tabla) {
			/* verificamos que se envio un nombre de tabla */
			if ($tabla === False)
				/* enviamos un error */
				$this->newException('No se especifico el nombre de la tabla');
			if (gettype($tabla) != 'string')
				/* enviamos un error */
				$this->newException('El nombre de la tabla debe ser un String');
		}

		private function validarInsert($tabla, $datos, $indices = False) {
			/* verificamos que se envio un nombre de tabla */
			$this->validarNombre($tabla);

			/* verificamos los datos */
			if ($datos === False)
				/* mostramos un mensaje de error */
				$this->newException('No se especifico los datos a almacenar');
			if (gettype($datos) != 'array')
				/* mostramos un mensaje de error */
				$this->newException('Los datos a almacenar deben ser pasados como Array');

			if ($indices) {
				/* recorremos los datos para verificar en indice */
				foreach ($datos AS $indice => $valor) {
					if (gettype($indice) != 'string')
						/* mostramos un mensaje de error */
						$this->newException('El indice de los datos deben ser pasados como String');
				}
			}
		}

		private function armarWhere($where, $toAdd) {
			/* agregamos el WHERE a la consulta */
			$this->addSQL(
				/* buscamos por otro WHERE */
				strtolower(substr($this->tempSQL, -5)) == 'where' ?
				/* si ya existe un WHERE agregamos un espacio */
				' ' . (
					/* si la cadena enviada tiene un WHERE */
					strtolower(substr($where, 0, 5)) == 'where' ?
					/* se lo eliminamos */
					substr($where, 5) :
					/* sino, agregamos la cadena como esta */
					$where
				/* si no tiene un WHERE */
				) : (
					/* verificamos si la cadena enviada tiene un WHERE */
					strtolower(substr($where, 0, 5)) == 'where' ?
					/* si ya tiene agregamos con el WHERE de la cadena original */
					$where :
					/* sino, agregamos la cadena como esta seguida del WHERE */
					$toAdd . $where
				)
			);
		}

		private function armarOrderBy($order, $asc) {
			/* agregamos la sentencia ORDER BY */
			$this->addSQL(
				/* si ya tiene un ORDER BY */
				strstr(strtolower($this->tempSQL), 'order by') ?
				/* agregamos una separacion */
				', ' :
				/* sino, agregamos el ORDER BY */
				' ORDER BY '
			);
			/* recorremos los campos */
			foreach ($order AS $campo)
				$this->addSQL("$campo, ");
			/* eliminamos la coma y espacio restantes */
			$this->cleanSQL();
			/* agregamos el sentido de orden */
			$this->addSQL(' ' . ($asc ? 'ASC' : 'DESC'));
		}

		private function armarLimit($limit, $cantidad) {
			/* agregamos el LIMIT a la consulta */
			$this->addSQL(' LIMIT ' . $limit);
			/* verificamos si envio cantidad */
			if ($cantidad !== False)
				/* la agregamos a la consulta */
				$this->addSQL(', ' . $cantidad);
		}

		private function armarSelectFrom($campos, $tabla, $showMy) {
			/* verificamos si envio campos para seleccionar */
			if ($campos === False && $showMy) {
				$this->show('No se especifico que campos seleccionar, se asume \'*\'', False);
				$campos = '*';
			/* verificamos el tipo de parametro enviado */
			} elseif (gettype($campos) != 'array')
				/* si no se envio como array los campos a seleccionar devolvemos un error */
				$this->newException('Los nombre de campos a seleccionar deben ser pasados como Array');
			else {
				/* en temp almacenaremos la cadena temporal del SELECT */
				$temp = '';
				/* recorremos todos los campos enviados como parametros */
				foreach ($campos AS $tits => $item)
					/* agregamos a la cadena temporal el campo actual */
					$temp .= (
						/* si el indice del array es string */
						gettype($tits) == 'string' ?
						/* el SELECT tendra ese campo de la siguiente manera: ..., campo1 AS nombreDeseado */
						"$tits AS $item" :
						/* sino agregamos el campo simplemente */
						$item
					/* agregamos la coma para separar los campos */
					) . ', ';
				/* eliminamos la coma y el espacio que sobra */
				$campos = substr($temp, 0, -2);
			}
			/* armamos la cabecera del SELECT */
			$this->addSQL(trim("SELECT $campos FROM $tabla"), True);
		}

		private function armarSelectFromJoin($campos, $deTabla, $tablas) {
			/* armamos los campos */
			$arrayCampos = Array();
			/* recorremos los campos */
			foreach ($campos AS $campo => $valor)
				$arrayCampos[] = (
					/* si el indice del array es string */
					gettype($campo) == 'string' ?
					/* el SELECT tendra ese campo de la siguiente manera tbl1.campo1, tbl2.campo2, tbl2.campo3 */
					$campo . '.' . $valor :
					/* sino agregamos el campo como tabla y un asterisco, tbl1.* */
					$valor . '.*'
				);
			/* armamos los JOIN */
			$join = $deTabla . ' JOIN ';
			/* verificamos si se envio correctamente los JOIN */
			foreach ($tablas AS $tabla => $on) {
				/* si el indice ($tabla) no es un string esta incorrecto */
				if (gettype($tabla) != 'string')
					$this->newException('Los JOIN no se enviaron correctamente');
			}
			/* recorremos las tablas */
			foreach ($tablas AS $tabla => $on)
				$join .= "$tabla ON $on JOIN ";
			/* eliminamos el ultimo JOIN de mas */
			$join = substr($join, 0, -5);
			/* llamamos al metodo con los parametros modificados */
			$this->selectFrom($arrayCampos, $join, False);
		}

		private function armarInsert($tabla, $datos) {
			/* asignamos el inicio de la consulta */
			$this->addSQL("INSERT INTO $tabla VALUES (");
			/* recorremos los campos */
			foreach ($datos AS $valor)
				/* agregamos el campo */
				$this->addSQL((
					gettype($valor) == 'integer' ?
					"$valor, " : (
						$valor === Null ?
						'Null, ' :
						'\'' . addslashes($valor) . '\', '
					)
				));
			/* limpiamos el SQL */
			$this->cleanSQL();
			/* finalizamos el INSERT */
			$this->addSQL(')');
		}

		private function armarInsertFields($tabla, $datos) {
			$fields = '';
			$values = '';
			/* asignamos el inicio de la consulta */
			$this->addSQL("INSERT INTO $tabla", True);
			/* recorremos los campos */
			foreach ($datos AS $campo => $valor) {
				/* agregamos el nombre del campo */
				$fields .= "$campo, ";
				/* agregamos el valor del campo */
				$values .= (
					/* si es entero */
					gettype($valor) == 'integer' ?
					/* lo agregamos como esta */
					"$valor, " : (
						$valor === Null ?
						'Null, ' :
						'\'' . addslashes($valor) . '\', '
					)
				);
			}
			/* eliminamos la coma y espaci de mas */
			$fields = substr($fields, 0, -2);
			$values = substr($values, 0, -2);
			/* agregamos a la cadena INSERT los campos y sus valores */
			$this->addSQL(" ($fields) VALUES ($values)");
		}

		private function armarUpdate($tabla, $campos) {
			/* armamos la cadena update */
			$this->addSQL("UPDATE $tabla SET ", True);
			/* recorremos los campos */
			foreach ($campos AS $indice => $valor) {
				/* agregamos el campo */
				$this->addSQL($indice . ' = ' . (
					/* si es un numero */
					gettype($valor) == 'integer' ?
					/* agregamos asi como esta */
					$valor : (
						/* sino, si es Null */
						$valor === Null ?
						/* agregamos la palabra Null */
						'Null' :
						/* sino, lo agregamos entre comillas */
						'\'' . addslashes($valor) . '\''
					)
				) . ', ');
			}
			/* limpiamos la consulta SQL */
			$this->cleanSQL();
		}

		private function addSQL($string = False, $empty = False) {
			if ($empty)
				/* vaciamos el SQL */
				$this->tempSQL = '';
			/* agregamos la cadena al SQL */
			$this->tempSQL .= $string;
		}

		private function cleanSQL() {
			/* eliminamos la coma y espacio de mas */
			$this->tempSQL = substr($this->tempSQL, 0, -2);
			/* eliminamos espacios dobles */
			while (strstr($this->tempSQL, '  '))
				$this->tempSQL = str_replace('  ', ' ', $this->tempSQL);
		}
	}
?>